'use strict';
var address = require('./address');
var clone = require('./utils').clone;
var isEnabled = require('./is-enabled');
var newDate = require('new-date');
var objCase = require('obj-case');
var traverse = require('@segment/isodate-traverse');
var type = require('./utils').type;
/**
* Initialize a new `Facade` with an `obj` of arguments.
*
* @param {Object} obj
* @param {Object} opts
*/
function Facade(obj, opts) {
opts = opts || {};
if (!('clone' in opts)) opts.clone = true;
if (opts.clone) obj = clone(obj);
if (!('traverse' in opts)) opts.traverse = true;
if (!('timestamp' in obj)) obj.timestamp = new Date();
else obj.timestamp = newDate(obj.timestamp);
Eif (opts.traverse) traverse(obj);
this.opts = opts;
this.obj = obj;
}
/**
* Mixin address traits.
*/
address(Facade.prototype);
/**
* Return a proxy function for a `field` that will attempt to first use methods,
* and fallback to accessing the underlying object directly. You can specify
* deeply nested fields too like:
*
* this.proxy('options.Librato');
*
* @param {string} field
*/
Facade.prototype.proxy = function(field) {
var fields = field.split('.');
field = fields.shift();
// Call a function at the beginning to take advantage of facaded fields
var obj = this[field] || this.field(field);
if (!obj) return obj;
if (typeof obj === 'function') obj = obj.call(this) || {};
if (fields.length === 0) return this.opts.clone ? transform(obj) : obj;
obj = objCase(obj, fields.join('.'));
return this.opts.clone ? transform(obj) : obj;
};
/**
* Directly access a specific `field` from the underlying object, returning a
* clone so outsiders don't mess with stuff.
*
* @param {string} field
* @return {*}
*/
Facade.prototype.field = function(field) {
var obj = this.obj[field];
return this.opts.clone ? transform(obj) : obj;
};
/**
* Utility method to always proxy a particular `field`. You can specify deeply
* nested fields too like:
*
* Facade.proxy('options.Librato');
*
* @param {string} field
* @return {Function}
*/
Facade.proxy = function(field) {
return function() {
return this.proxy(field);
};
};
/**
* Utility method to directly access a `field`.
*
* @param {string} field
* @return {Function}
*/
Facade.field = function(field) {
return function() {
return this.field(field);
};
};
/**
* Proxy multiple `path`.
*
* @param {string} path
* @return {Array}
*/
Facade.multi = function(path) {
return function() {
var multi = this.proxy(path + 's');
if (type(multi) === 'array') return multi;
var one = this.proxy(path);
if (one) one = [this.opts.clone ? clone(one) : one];
return one || [];
};
};
/**
* Proxy one `path`.
*
* @param {string} path
* @return {*}
*/
Facade.one = function(path) {
return function() {
var one = this.proxy(path);
if (one) return one;
var multi = this.proxy(path + 's');
if (type(multi) === 'array') return multi[0];
};
};
/**
* Get the basic json object of this facade.
*
* @return {Object}
*/
Facade.prototype.json = function() {
var ret = this.opts.clone ? clone(this.obj) : this.obj;
if (this.type) ret.type = this.type();
return ret;
};
/**
* Get the options of a call (formerly called "context"). If you pass an
* integration name, it will get the options for that specific integration, or
* undefined if the integration is not enabled.
*
* @param {string} [integration]
* @return {Object or Null}
*/
Facade.prototype.options = function(integration) {
var obj = this.obj.options || this.obj.context || {};
var options = this.opts.clone ? clone(obj) : obj;
if (!integration) return options;
if (!this.enabled(integration)) return;
var integrations = this.integrations();
var value = integrations[integration] || objCase(integrations, integration);
if (typeof value !== 'object') value = objCase(this.options(), integration);
return typeof value === 'object' ? value : {};
};
Facade.prototype.context = Facade.prototype.options;
/**
* Check whether an integration is enabled.
*
* @param {string} integration
* @return {boolean}
*/
Facade.prototype.enabled = function(integration) {
var allEnabled = this.proxy('options.providers.all');
if (typeof allEnabled !== 'boolean') allEnabled = this.proxy('options.all');
if (typeof allEnabled !== 'boolean') allEnabled = this.proxy('integrations.all');
if (typeof allEnabled !== 'boolean') allEnabled = true;
var enabled = allEnabled && isEnabled(integration);
var options = this.integrations();
// If the integration is explicitly enabled or disabled, use that
// First, check options.providers for backwards compatibility
Iif (options.providers && options.providers.hasOwnProperty(integration)) {
enabled = options.providers[integration];
}
// Next, check for the integration's existence in 'options' to enable it.
// If the settings are a boolean, use that, otherwise it should be enabled.
if (options.hasOwnProperty(integration)) {
var settings = options[integration];
if (typeof settings === 'boolean') {
enabled = settings;
} else {
enabled = true;
}
}
return !!enabled;
};
/**
* Get all `integration` options.
*
* @api private
* @param {string} integration
* @return {Object}
*/
Facade.prototype.integrations = function() {
return this.obj.integrations || this.proxy('options.providers') || this.options();
};
/**
* Check whether the user is active.
*
* @return {boolean}
*/
Facade.prototype.active = function() {
var active = this.proxy('options.active');
if (active === null || active === undefined) active = true;
return active;
};
/**
* Get `sessionId / anonymousId`.
*
* @api public
* @return {*}
*/
Facade.prototype.anonymousId = function() {
return this.field('anonymousId') || this.field('sessionId');
};
Facade.prototype.sessionId = Facade.prototype.anonymousId;
/**
* Get `groupId` from `context.groupId`.
*
* @api public
* @return {string}
*/
Facade.prototype.groupId = Facade.proxy('options.groupId');
/**
* Get the call's "super properties" which are just traits that have been
* passed in as if from an identify call.
*
* @param {Object} aliases
* @return {Object}
*/
Facade.prototype.traits = function(aliases) {
var ret = this.proxy('options.traits') || {};
var id = this.userId();
aliases = aliases || {};
if (id) ret.id = id;
for (var alias in aliases) {
var value = this[alias] == null ? this.proxy('options.traits.' + alias) : this[alias]();
Iif (value == null) continue;
ret[aliases[alias]] = value;
delete ret[alias];
}
return ret;
};
/**
* Add a convenient way to get the library name and version
*/
Facade.prototype.library = function() {
var library = this.proxy('options.library');
if (!library) return { name: 'unknown', version: null };
if (typeof library === 'string') return { name: library, version: null };
return library;
};
/**
* Return the device information or an empty object
*
* @return {Object}
*/
Facade.prototype.device = function() {
var device = this.proxy('context.device');
if (type(device) !== 'object') device = {};
var library = this.library().name;
if (device.type) return device;
if (library.indexOf('ios') > -1) device.type = 'ios';
if (library.indexOf('android') > -1) device.type = 'android';
return device;
};
/**
* Set up some basic proxies.
*/
Facade.prototype.userAgent = Facade.proxy('context.userAgent');
Facade.prototype.timezone = Facade.proxy('context.timezone');
Facade.prototype.timestamp = Facade.field('timestamp');
Facade.prototype.channel = Facade.field('channel');
Facade.prototype.ip = Facade.proxy('context.ip');
Facade.prototype.userId = Facade.field('userId');
/**
* Return the cloned and traversed object
*
* @param {*} obj
* @return {*}
*/
function transform(obj) {
return clone(obj);
}
/**
* Exports.
*/
module.exports = Facade;
|